---@diagnostic disable: param-type-mismatch
-- 当前waf的一个情况报告
-- 模板位置 {easy_ngx_waf目录}/tpl/report.html

-- 使用方法示例:
-- **由于此报告未做任何鉴权,以防止WAF配置信息泄露,
-- **不使用时强烈建议删除/注释掉以下location配置;
--     在 nginx.conf 某个 server 段添加 location
--       server {
--           server_name www.example.xxx
--           ...
--           location = /waf_report {
--               content_by_lua_file {当前report.lua文件绝对路径};
--           }
--       }
--     直接访问  http://www.example.xxx/waf_report 即可查看;

local config = require("config")
local util = require("lib.util")
local moses = require("lib.packages.moses_min")

-- 指定 shared_dict 变量(_dict_var)使用情况
--      返回 shared_dict.DICT 的  容量, 空闲空间, key数量
--      如果 shared_dict.DICT不存在,返回 nil,'错误提示信息'
-- @param _dict_var string 共享变量名称
local function share_dict_info(_dict_var)
    local _dict = ngx.shared[_dict_var]
    if not _dict then
        return nil, _dict_var .. " does not exist."
    end
    local keys = _dict:get_keys(0)
    local key_count = 0
    if type(keys) == "table" then
        for _ in ipairs(keys) do
            key_count = key_count + 1
        end
    end
    return _dict:capacity(), _dict:free_space(), key_count
end

--- 获取模板文件内容
--      获取失败返回 nil, '失败信息'
---@param tpl_file_path string  模板文件路径(绝对路径)
---@return string | nil
local function read_tpl_content(tpl_file_path)
    local fh = io.open(tpl_file_path, "r")
    if fh == nil then
        return nil, "tpl open failed!"
    end
    local tpl_content = fh:read("*a")
    fh:close()
    return tpl_content
end

-- 渲染模板为最终html
-- @param tpl_content string 模板内容
-- @param vars_tbl table 模板中需要用到的变量表
-- @return string
local function render_html(tpl_content, vars_tbl)
    local etlua = require("lib.packages.etlua")
    local template = etlua.compile(tpl_content)
    return template(vars_tbl)
end

-- 生成报告模板需要用到的变量表
-- @return table
local function build_report_vars()
    local vars = {}
    vars.lua_version = _VERSION
    vars.nginx_version = ngx.var.nginx_version
    vars.config = config
    vars.util = util

    local easy_waf_cc_dict_capacity, easy_waf_cc_dict_free, easy_waf_cc_dict_keys = share_dict_info("easy_waf_cc_dict")
    if not easy_waf_cc_dict_capacity then
        -- "easy_waf_cc_dict" 共享变量不存在
        vars.easy_waf_cc_dict = nil
    else
        vars.easy_waf_cc_dict = {
            capacity = util.num2thousandth_str(easy_waf_cc_dict_capacity)
                .. " <small>(" .. util.byte2human_str(easy_waf_cc_dict_capacity) .. ")</small>",
            free = util.num2thousandth_str(easy_waf_cc_dict_free)
                .. " <small>(" .. util.byte2human_str(easy_waf_cc_dict_free) .. ")</small>",
            keys = easy_waf_cc_dict_keys,
        }
    end

    local easy_waf_cache_capacity, easy_waf_cache_free, easy_waf_cache_keys = share_dict_info(
        "easy_waf_cache")
    if not easy_waf_cache_capacity then
        -- "easy_waf_cache" 共享变量不存在
        vars.easy_waf_cache = nil
    else
        local tmp = {}
        for _, key in pairs(ngx.shared.easy_waf_cache:get_keys(0)) do
            table.insert(tmp, { key, tostring(ngx.shared.easy_waf_cache:get(key)) })
        end
        vars.easy_waf_cache = {
            capacity = util.num2thousandth_str(easy_waf_cache_capacity)
                .. " <small>(" .. util.byte2human_str(easy_waf_cache_capacity) .. ")</small>",
            free = util.num2thousandth_str(easy_waf_cache_free)
                .. " <small>(" .. util.byte2human_str(easy_waf_cache_free) .. ")</small>",
            keys = easy_waf_cache_keys,
            all_value = moses.sort(tmp, function(a, b)
                return a[1] < b[1]
            end)
        }
    end

    return vars
end

local html = nil
local tpl_content, err = read_tpl_content(config.waf_base_path .. "/tpl/report.html")
if not tpl_content then
    html = err
else
    local report_vars = build_report_vars()
    html = render_html(tpl_content, report_vars)
end

ngx.say(html)
ngx.exit(200)
